package com.zito.health.api.aspect;

import com.zito.health.api.annotation.AppGetMapping;
import com.zito.health.api.annotation.AppPostMapping;
import com.zito.health.api.exception.AuthenticationException;
import com.zito.health.api.exception.UnauthorizedException;
import com.zito.health.api.result.Constants;
import com.zito.health.api.utils.JedisUtil;
import com.zito.health.entity.User;
import com.zito.health.service.UserService;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

@Aspect
@Component
public class Authentication {
    private static final Logger logger = LoggerFactory.getLogger(Authentication.class);

    @Autowired
    private HttpServletRequest req;

    @Autowired
    private UserService userService;

    @Before("@annotation(agm)")
    public void doGet(AppGetMapping agm) throws AuthenticationException {
        filter(0, agm.value(), agm.note());
    }

    @Before("@annotation(apm)")
    public void doPost(AppPostMapping apm) throws AuthenticationException {
        filter(1, apm.value(), apm.note());
    }

    private void filter(int method, String uri, String note) throws AuthenticationException {

        // 写个log 记录一下 感觉没用 就注释掉
        logger.info("正在执行对 {}:{}， 接口说明 [{}] 的通行检查。", new String[]{"GET", "POST"}[method], uri, note);

        // 按理说 这里是不是应该再预留个 从缓存中获取用户信息，省的再麻烦数据库了。 要求的时候再写吧

        // 拿 Authorization 头，协议使用了自定义的 APP-TOKEN，格式类似 Bearer 为 APP-TOKEN UUIDv4
        String token = req.getHeader("Authorization");

        // token 为空或不符合规则皆为未登录
        if (null == token || !token.matches(
                "^APP-TOKEN [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
        )) throw new UnauthorizedException("无法取得有效的 Token");

            // 10 == "TOP-TOKEN ".length()
        else token = token.substring(10);

        // Redis 直读，Exception 应该也不需处理吧 既然你都存进去了 取还取不出来么
        final Integer suid = (Integer) JedisUtil.getObject(Constants.APP_TOKEN + token);

        // token 不存在可能是无效的构造 token 或已过期，都直接给未登录状态，不给具体信息  给具体信息太麻烦了。不符合一切从简的理念
        if (null == suid) throw new UnauthorizedException("该 Token 不存在或已过期");

        // 拉数据库取对应的 User，后续起量再套 @Cache 吧，现在开发来不及只好直接压数据库上
        final User user = userService.queryOne(new User() {{
            setUserid(suid);
        }});

        // 用户不存在可能是用户被删除或封禁或 Redis 里头的 uid 被修改，真是这样 那也没法办 被有些东西限制的死死的
        if (null == user) throw new UnauthorizedException("该用户不存在或已被封禁");

        // 刷新活跃，Token 相关操作结束
        JedisUtil.setTtl(Constants.APP_TOKEN + token, 604800);

    }

}

